# tell/__init__.py
#
#

"""

    T E L L -=- I T -=- AS -=- IT -=- IS !!!

    -=-

    (this code is best enjoyed on a wide screen)

"""

## T E L L  version

__version__ = 1

import os

## alias

j = os.path.join

## mj function - module joins

def mj(*args): return j(*args).replace(os.sep, ".")

## tell imports

from .utils import make_opts, make_plugin, resolve_ip, make_datadir, notskip, hello, get_name, enc_needed, enc_name, error, get_where, callstack
from .utils import get_time, enc_char
from .errors import NoFileName, NoArgument, NoAttribute, NotSameType, OverloadError, RatherNot, NoTask, NoFunc, MissingError
from .errors import NoCommand, Denied, UnknownType, NoExec, NoInput, TryAgain, NotImplemented, WrongOType
from .templates import headertxt
from .defines import jsontypes, otypes, bottypes, default_attrs

## basic imports 

import collections
import threading
import getpass
import logging
import hashlib
import fcntl
import types
import errno
import uuid
import json
import time
import imp
import sys
import os

## basics

ip = resolve_ip()
shelluser = getpass.getuser()
kernel = None

## Big O

class O(dict):

    """ basic class providing dotted access to a dict and json dump. """

    ## basic methods 

    def __init__(self, *args, **kwargs):
        dict.__init__(self, **kwargs)
        if type(self) not in jsontypes: jsontypes.append(type(self))

    


    def get_type(self):
        return '%s.%s' % (
            self.__class__.__module__,
            self.__class__.__name__,
        )

    def id(self): return "%s,%s,%s,%s,%s,%s" % (self.uuid, ip, self.name, self.get_type(), self.target, time.time())

    def get_uuid(self):
        if not "uuid" in self: self["uuid"] = str(uuid.uuid4())
        return self["uuid"]

    def get_path(self):
        """ make the path of this file. encode the file part if badchars are encountered. """
        if not "filename" in self: self.filename = self.id()
        try: head, fn = os.path.split(os.path.abspath(j(self.root, self.filename)))
        except AttributeError: raise NoFileName(self.filename)
        if not fn: return head
        from .utils import make_dir
        make_dir(head)
        return j(head, enc_char(fn))

    def __getattr__(self, name):
        try: return self[name]
        except KeyError:
            if name == "kernel": return get_kernel()
            if name == "uuid": return self.get_uuid()
            if name == "otype": return str(type(self))
            if name == "name": return "noname"
            self.init(name)
        try: return self[name]
        except KeyError: return None

    def __setattr__(self, name, value):
        """ set an attribute, check if no method is overridden and if same types get assigned (string can be overridden). """
        t = type(value)
        if name in self and t in [types.MethodType, types.FunctionType]: raise OverloadError(name)
        if t != "" and name in self and type(value) != t: raise NotSameType(name)
        self[name] = value
        return self

    def __call__(self, *args, **kwargs): raise NotImplemented()

    def __exists__(self, a):
        try: self[a] ; return True
        except KeyError: False

    ## O based construction and default attributes

    def init(self, name, *args, **kwargs):
        if name not in self:
            if name == "root": self.chill()
            if name == "result": self[name] = O(name="result")
            if name == "status": self[name] = O(name="status")
            if name == "errors": self[name] = O(name="errors")
            if name == "cc": self[name] = ";"
            if name == "ctime": self[name] = time.time()
            if name == "_ready": self[name] = threading.Event()
            if name == "bots": self["bots"] = []
            #if name not in self: raise MissingError(name)
        return self

    ## add methods

    def add_error(self, txt): self.errors[get_time()] = txt

    def add_status(self, txt): self.status[get_time()] = txt      

    ## output methods

    def direct(self, txt): self.bot._raw(txt)

    def reply(self, txt, *args, **kwargs):
        """ add a txt string to the result attribute. this attribute gets returned when ready. use wait() for that. """
        res = O(**self)
        res.otype = "reply"
        res.txt = txt
        res.channel = self.channel
        self.result[str(time.ctime(time.time()))] = res

    def display(self, txt="", target=None):
        """ display results via self._bot """
        if not target: target = self.say
        for key in self.result.strip():
            item = self.result[key]
            try: target(item.channel, item.txt)
            except AttributeError:
                try: target(item.txt)
                except AttributeError: continue
        for key in self.errors.strip():
            item = self.errors[key]
            try: target("error:" + " " + item)
            except AttributeError: continue

    def say(self, *args, **kwargs):
        self.bot.say(*args, **kwargs)

    ## wait for results

    def wait(self, sec=3.0):
        """ wait for the results to show up. """
        self.prepare()
        self._ready.wait(sec)
        if not self.result: raise TryAgain()
        return self

    ## JSON display functions 

    def full(self, *args, **kwargs):
        """ convert the current object (dict) to json string, replacing non convertable data to it's string representation (utils.get_name). """
        return json.dumps(self, default=utils.get_name, *args, **kwargs)

    def json(self, *args, **kwargs):
        """ convert the current object (dict) to json string, replacing non convertable data to it's string representation (utils.get_name). """
        return json.dumps(self.strip(["bot", "result", "status", "kernel"], "_"), default=utils.get_name, *args, **kwargs)

    ## helper methods

    def prepare(self):
        if "txt" not in self: self.parse()
        if "txt" in self:
            try:
                if self.hascc(): self.cmnd = self.txt.split()[0][1:]
                self.args = self.txt.split()[1:]
                self.rest = " ".join(self.args)
            except ValueError: self.args = [] ; self.rest = ""
        return self

    def copyin(self, input):
        """ copy in a dict. """
        target = O(**input)
        self.update(target)

    def strip(self, ignore=[], ignorestart=""):
        """ remove attributes starting with _ """
        return { k: self[k] for k in self if not k[0] in ignorestart and not k in ignore }

    def make_signature(self, sig=None):
        """ create a signature of current data, so we can ensure originality when needed. """
        return str(hashlib.sha1(bytes(str(sig or self), "utf-8")).hexdigest())

    def hascc(self, input=None):
        """ check if event.txt begins with a control character (cc). indicates a command given. """
        if not input: input = self.txt
        return input[0] in self.cc

    def done(self):
        """ signal when the event is processed. """
        self._ready.set() 

    def iters(self):
        for item in self.values():
            try: item.__iter__ ; yield item
            except AttributeError: continue

    def parse(self, *args, **kwargs):
        if "input" in self: self.txt = "%s" % self.input

    def chill(self):
        homedir = os.path.expanduser("~")
        if "datadir" not in self: self.datadir = j(homedir, ".tell")
        self.root = j(homedir, self.datadir)
        if not self.root in sys.path: sys.path.append(self.root)

## OL class

class OL(collections.deque): pass

## boot tell

def get_kernel():
    from tell.runtime import Kernel
    global kernel
    if not kernel: kernel = Kernel(name="kernel", otype="runtime")
    return kernel

kernel = get_kernel()

#### C O M M E N T  -=-  S E C T I O N

""" please write comments on changes you made below, so we can get log of things added/changed. """

## BHJTW 27-11-2012 initial import 
